'use strict'

const assert = require('node:assert/strict')

const { describe, it } = require('mocha')

const { stringifyWithRanges } = require('../../../../src/appsec/iast/vulnerabilities-formatter/utils')

describe('test vulnerabiilty-formatter utils', () => {
  describe('stringifyWithRanges', () => {
    describe('loadSensitiveRanges = false', () => {
      it('Undefined ranges', () => {
        const src = { key: 'value' }
        const { value, ranges } = stringifyWithRanges(src)

        assert.strictEqual(value, JSON.stringify(src, null, 2))
        assert.strictEqual(ranges.length, 0)
      })

      it('Empty ranges', () => {
        const src = { key: 'value' }
        const { value, ranges } = stringifyWithRanges(src, {})

        assert.strictEqual(value, JSON.stringify(src, null, 2))
        assert.strictEqual(ranges.length, 0)
      })

      it('Range in first level', () => {
        const src = { key: 'value' }
        const iinfo = { type: 'TEST' }
        const range = {
          start: 0, end: 5, iinfo
        }

        const { value, ranges } = stringifyWithRanges(src, { key: [range] })

        assert.strictEqual(value, JSON.stringify(src, null, 2))

        assert.deepStrictEqual(ranges, [
          { start: 12, end: 17, iinfo }
        ])
      })

      it('Range in first level and other in nested level', () => {
        const src = {
          key: 'value',
          withChildren: {
            notTainted: 'not tainted',
            partiallyTainted: 'nnnYYYYYnnnYYY' // YYYYY is tainted
          }
        }
        const iinfo = { type: 'TEST' }
        const firstRanges = [{
          start: 0, end: 5, iinfo
        }]
        const secondRanges = [
          { start: 3, end: 8, iinfo },
          { start: 11, end: 14, iinfo }
        ]

        const { value, ranges } = stringifyWithRanges(src, {
          key: firstRanges,
          'withChildren.partiallyTainted': secondRanges
        })

        assert.strictEqual(value, JSON.stringify(src, null, 2))

        assert.deepStrictEqual(ranges, [
          { start: 12, end: 17, iinfo },
          { start: 101, end: 106, iinfo },
          { start: 109, end: 112, iinfo }
        ])
      })

      it('Empty ranges in array', () => {
        const src = [{ key: ['value'] }]
        const { value, ranges } = stringifyWithRanges(src, {})

        assert.strictEqual(value, JSON.stringify(src, null, 2))
        assert.strictEqual(ranges.length, 0)
      })

      it('Range in array item', () => {
        const src = ['value']
        const iinfo = { type: 'TEST' }
        const range = {
          start: 0, end: 5, iinfo
        }
        const { value, ranges } = stringifyWithRanges(src, { 0: [range] })

        assert.strictEqual(value, JSON.stringify(src, null, 2))

        assert.deepStrictEqual(ranges, [
          { start: 5, end: 10, iinfo }
        ])
      })

      it('Ranges in array and nested objects', () => {
        const src = [
          {
            key: 'tainted1'
          },
          'tainted2',
          'nnnYYYYnYYn',
          {
            key2: [{
              key21: 'nnYYn',
              key22: 'nottainted'
            }]
          },
          'nottainted'
        ]
        const iinfo = { type: 'TEST' }
        const objRanges = {
          '0.key': [{ start: 0, end: 8, iinfo }],
          1: [{ start: 0, end: 8, iinfo }],
          2: [
            { start: 3, end: 7, iinfo },
            { start: 8, end: 10, iinfo }
          ],
          '3.key2.0.key21': [{ start: 2, end: 4, iinfo }]
        }

        const { value, ranges } = stringifyWithRanges(src, objRanges)

        assert.strictEqual(value, JSON.stringify(src, null, 2))

        assert.deepStrictEqual(ranges, [
          { start: 18, end: 26, iinfo },
          { start: 36, end: 44, iinfo },
          { start: 53, end: 57, iinfo },
          { start: 58, end: 60, iinfo },
          { start: 110, end: 112, iinfo }
        ])
      })

      it('Very deep object', () => {
        const deepObject = [...Array(100).keys()]
          .reduce((obj) => ({ key: 'value', obj: obj || {} }), null)

        const expectedObject = [...Array(10).keys()]
          .reduce((obj) => ({ key: 'value', obj: obj || {} }), null)

        const iinfo = { type: 'TEST' }
        const deepestKey = '0'.concat('.obj'.repeat(9)).concat('.key')
        const objRanges = {
          [deepestKey]: [{ start: 0, end: 5, iinfo }]
        }

        const { value, ranges } = stringifyWithRanges([deepObject], objRanges)

        assert.strictEqual(value, JSON.stringify([expectedObject], null, 2))

        assert.deepStrictEqual(ranges, [
          { start: 477, end: 482, iinfo }
        ])
      })

      it('Circular reference object', () => {
        const circularObject = { key: 'value' }
        circularObject.obj = circularObject

        const expectedObject = [{ key: 'value' }]

        const iinfo = { type: 'TEST' }
        const objRanges = {
          '0.key': [{ start: 0, end: 5, iinfo }]
        }

        const { value, ranges } = stringifyWithRanges([circularObject], objRanges)

        assert.strictEqual(value, JSON.stringify(expectedObject, null, 2))

        assert.deepStrictEqual(ranges, [
          { start: 18, end: 23, iinfo }
        ])
      })
    })

    describe('loadSensitiveRanges = true', () => {
      it('Undefined ranges', () => {
        const src = { key: 'value', numberKey: 123, nullKey: null, falseKey: false, trueKey: true }

        const { value, ranges, sensitiveRanges } = stringifyWithRanges(src, null, true)

        assert.strictEqual(value, JSON.stringify(src, null, 2))
        assert.strictEqual(ranges.length, 0)

        assert.deepStrictEqual(sensitiveRanges, [
          { start: 12, end: 17 },
          { start: 35, end: 38 },
          { start: 53, end: 57 },
          { start: 73, end: 78 },
          { start: 93, end: 97 }
        ])
      })

      it('Range in first level', () => {
        const src = { key: 'value' }
        const iinfo = { type: 'TEST' }
        const range = {
          start: 0, end: 5, iinfo
        }

        const { value, ranges, sensitiveRanges } = stringifyWithRanges(src, { key: [range] }, true)

        assert.strictEqual(value, JSON.stringify(src, null, 2))

        assert.deepStrictEqual(ranges, [
          { start: 12, end: 17, iinfo }
        ])

        assert.deepStrictEqual(sensitiveRanges, [
          { start: 12, end: 17 }
        ])
      })

      it('Sensitive key', () => {
        const src = { 'bearer zss8dR9QP81A': 'value' }

        const { value, ranges, sensitiveRanges } = stringifyWithRanges(src, {}, true)

        assert.strictEqual(value, JSON.stringify(src, null, 2))
        assert.strictEqual(ranges.length, 0)

        assert.deepStrictEqual(sensitiveRanges, [
          { start: 5, end: 24 },
          { start: 28, end: 33 }
        ])
      })

      it('Range in first level and other in nested level', () => {
        const src = {
          key: 'value',
          withChildren: {
            notTainted: 'not tainted',
            partiallyTainted: 'nnnYYYYYnnnYYY' // YYYYY is tainted
          }
        }
        const iinfo = { type: 'TEST' }
        const firstRanges = [{
          start: 0, end: 5, iinfo
        }]
        const secondRanges = [
          { start: 3, end: 8, iinfo },
          { start: 11, end: 14, iinfo }
        ]

        const { value, ranges, sensitiveRanges } = stringifyWithRanges(src, {
          key: firstRanges,
          'withChildren.partiallyTainted': secondRanges
        }, true)

        assert.strictEqual(value, JSON.stringify(src, null, 2))

        assert.deepStrictEqual(ranges, [
          { start: 12, end: 17, iinfo },
          { start: 101, end: 106, iinfo },
          { start: 109, end: 112, iinfo }
        ])

        assert.deepStrictEqual(sensitiveRanges, [
          { start: 12, end: 17 },
          { start: 59, end: 70 },
          { start: 98, end: 112 }
        ])
      })

      it('Range in first level, other in nested level and sensitive key', () => {
        const src = {
          key: 'value',
          withChildren: {
            'bearer zss8dR9QP81A': 'not tainted',
            partiallyTainted: 'nnnYYYYYnnnYYY' // YYYYY is tainted
          }
        }
        const iinfo = { type: 'TEST' }
        const firstRanges = [{
          start: 0, end: 5, iinfo
        }]
        const secondRanges = [
          { start: 3, end: 8, iinfo },
          { start: 11, end: 14, iinfo }
        ]

        const { value, ranges, sensitiveRanges } = stringifyWithRanges(src, {
          key: firstRanges,
          'withChildren.partiallyTainted': secondRanges
        }, true)

        assert.strictEqual(value, JSON.stringify(src, null, 2))

        assert.deepStrictEqual(ranges, [
          { start: 12, end: 17, iinfo },
          { start: 110, end: 115, iinfo },
          { start: 118, end: 121, iinfo }
        ])

        assert.deepStrictEqual(sensitiveRanges, [
          { start: 12, end: 17 },
          { start: 45, end: 64 },
          { start: 68, end: 79 },
          { start: 107, end: 121 }
        ])
      })

      it('Empty ranges in array', () => {
        const src = [{ key: ['value'] }]

        const { value, ranges, sensitiveRanges } = stringifyWithRanges(src, {}, true)

        assert.strictEqual(value, JSON.stringify(src, null, 2))
        assert.strictEqual(ranges.length, 0)

        assert.deepStrictEqual(sensitiveRanges, [
          { start: 26, end: 31 }
        ])
      })

      it('Range in array item', () => {
        const src = ['value']
        const iinfo = { type: 'TEST' }
        const range = {
          start: 0, end: 5, iinfo
        }

        const { value, ranges, sensitiveRanges } = stringifyWithRanges(src, { 0: [range] }, true)

        assert.strictEqual(value, JSON.stringify(src, null, 2))

        assert.deepStrictEqual(ranges, [
          { start: 5, end: 10, iinfo }
        ])

        assert.deepStrictEqual(sensitiveRanges, [
          { start: 5, end: 10 }
        ])
      })

      it('Ranges in array and nested objects', () => {
        const src = [
          {
            key: 'tainted1'
          },
          'tainted2',
          'nnnYYYYnYYn',
          {
            key2: [{
              key21: 'nnYYn',
              key22: 'nottainted'
            }]
          },
          'nottainted'
        ]
        const iinfo = { type: 'TEST' }
        const objRanges = {
          '0.key': [{ start: 0, end: 8, iinfo }],
          1: [{ start: 0, end: 8, iinfo }],
          2: [
            { start: 3, end: 7, iinfo },
            { start: 8, end: 10, iinfo }
          ],
          '3.key2.0.key21': [{ start: 2, end: 4, iinfo }]
        }

        const { value, ranges, sensitiveRanges } = stringifyWithRanges(src, objRanges, true)

        assert.strictEqual(value, JSON.stringify(src, null, 2))

        assert.deepStrictEqual(ranges, [
          { start: 18, end: 26, iinfo },
          { start: 36, end: 44, iinfo },
          { start: 53, end: 57, iinfo },
          { start: 58, end: 60, iinfo },
          { start: 110, end: 112, iinfo }
        ])

        assert.deepStrictEqual(sensitiveRanges, [
          { start: 18, end: 26 },
          { start: 36, end: 44 },
          { start: 50, end: 61 },
          { start: 108, end: 113 },
          { start: 134, end: 144 },
          { start: 168, end: 178 }
        ])
      })
    })
  })
})
